package a2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

/**
 * The Token Class defines variable and methods used to store parsed input
 * using the following grammar:
 *  Program -> {VariableDefinition} {FunctionDefinition} Expr
 *  VariableDefinition -> let Identifier Expr 
 *  FunctionDefinition -> define Signature Program 
 *  Signature -> Identifier ( {Identifier} )
 *  Expr -> StringLiteral | Identifier | Application
 *  Application -> @pply FunctionName ( {Expr} )
 *  FunctionName -> Identifier | + | ~
 * 
 * Note: Identifiers are StringLiterals that begin with a capital letter
 * @version 1.0
 * @author Michael Sawaya
 */
public class Token
{

    /**
     * The type of the token
     */
    private String type;
    /**
     * The spelling of the token
     */
    private String spelling;
    /**
     * List of terminal symbols, presorted
     */
    private static final String[] terminals =
    {
        "(", ")", "+", "apply", "define", "let", "return", "~"
    };    
    /**
     * End of input
     */
    public static final String END_OF_STATEMENT = "end-of-input";

    /**
     * Defines a new Token by giving its Type and 
     * Spelling as parameters
     * @param type
     * @param spelling 
     */
    public Token(String type, String spelling)
    {
        this.type = type;
        this.spelling = spelling;
    }

    /**
     * Defines a new Token using input for both type and spelling
     * @param input 
     */
    public Token(String input)
    {
        type = input;
        spelling = input;
    }

    /**
     * Returns the spelling of the Token
     * @return the spelling of the Token
     */
    public String getSpelling()
    {
        return spelling;
    }

    /**
     * Returns the type of the token
     * @return the type of the token
     */
    public String getType()
    {
        return type;
    }

    /**
     * Identifies the token type for the given spelling.
     * @return The token type.
     */
    public static String getTokenType(String input)
    {
        String spelling = input;
        if (spelling == null || spelling.length() == 0)
        {
            throw new IllegalArgumentException("Please give a non-empty or non-"
                    + "null argument.");
        }
        if (Arrays.binarySearch(terminals, input) >= 0)
        {
            return spelling;
        }
        if (isIdentifier(spelling))
        {
            return "Identifier";
        }
        if (isStringLiteral(spelling))
        {
            return "StringLiteral";
        }
        throw new IllegalArgumentException("The token " + input + " is not form"
                + "ated correctly. Please make sure that it starts with a non-"
                + "numeric character.");
    }

    /**
     * Checks the input string whether it is of Identifier type.
     * @param input The input string.
     * @return True if it is an Identifier.
     */
    public static boolean isIdentifier(String input)
    {
        if (input == null || input.equals(""))
        {
            return false;
        }
        return Character.isUpperCase(input.charAt(0));
    }

    /**
     * Checks the input string whether it is of StringLiteral type.
     * @param input The input string.
     * @return True if it is an StringLiteral.
     */
    public static boolean isStringLiteral(String input)
    {
        if (input == null || input.equals(""))
        {
            return false;
        }
        return Character.isLowerCase(input.charAt(0));
    }

    /**
     * Returns a string representation of the Token in the form:
     * "type:spelling"
     * @return a string representation of the Token.
     */
    @Override
    public String toString()
    {
        return type + ":" + spelling;
    }

    /**
     * Returns a token list variation of the input.
     * @param input The string to be tokenized
     * @return input as a Token List 
     */
    public static List<Token> tokenize(String input)
    {
        Scanner in;
        try
        {
            in = new Scanner(input); //Creates a new Scanner with the input
        }
        catch (NullPointerException e)
        {
            return null;
        }
        List<Token> output = new ArrayList<Token>();
        //Scan the string and create a token object for each delimited token
        while (in.hasNext())
        {
            String next = in.next();
            output.add(new Token(getTokenType(next), next ));
        }
        output.add(new Token(END_OF_STATEMENT));
        return output;
    }
}
